# build in python 3.6
# 作者：陈常鸿
# 回测引擎
# last change on 20181203
from hashakisea.engine.brokers.backtestBroker import BacktestBroker
from hashakisea.constant.objectClass import barClass,tradeClass,orderClass,StopOrder,ResultClass
from hashakisea.constant.chinese import (DIRECTION_LONG,DIRECTION_SHORT,STOPORDER_TRIGGERED,
STATUS_ALLTRADED,STATUS_NOTTRADED,STATUS_CANCELLED,CTAORDER_BUY,OFFSET_OPEN,CTAORDER_SELL,OFFSET_CLOSE,
CTAORDER_SHORT,CTAORDER_COVER)

from collections import deque,OrderedDict,defaultdict
import traceback
from math import floor

class BacktestingEngine:
    def __init__(self):
        self.__broker=BacktestBroker(self)
        self.mode='BAR'             # 默认是bar模式
        self.strategy=None          # 以防策略为空，后面作判断使用
        self.analycer=None          # 以防为空被调用，后面作判断
        self.indicator=None         # 同上

        # 在回测前，用户需要给的设置
        self.initcapital=None       # 初始金额 初始金额在broker中实现
        self.capital = None         # 总资产
        self.cash = None            # 现金
        self.start_time=''          # 回测数据开始数据 20181022
        self.end_time=''            # 回测数据截至时间 20181022
        self.slippage = 0           # 回测时假设的滑点
        self.rate = 0               # 回测时假设的佣金比例（适用于百分比佣金）
        self.size = 1               # 合约大小，默认为1        
        self.priceTick = 0.001     # 价格最小变动
        self.marketMoney=0.0        # 持仓的基础货币

        # -----------------------------------------------------------------------

        self.count_tick=0                   # 第几根base_bar
        self.limitOrderCount = 0.0
        self.stopOrderCount=0
        self.bar_num=0                      # bar的总根数,用于算市场波动/hashaki
        self.dt=''                          # 现在时间
        self.tradeCount = 0                 # 成交编号
        self.pos=0.0
        self.deposit=0.0

        self.tradeDict = OrderedDict()      # 成交字典
        self.longPosition = []              # 多单持仓
        self.shortPosition = []             # 空单持仓
        self.limitOrderDict = OrderedDict()         # 限价单字典
        self.workingLimitOrderDict = OrderedDict()  # 活动限价单字典，用于进行撮合用
        self.marketValueList = list()    # 钱在市场,现货可用
        self.cashList=list()             # 口袋里的钱
        self.capitalList=list()          # 总资产
        self.posList=list()
        self.depositList=list()
        self.result=ResultClass()        # {'开仓时间':'','平仓时间':'','买进金额':[],...}
        self.resultList=[]

        # 本地停止单字典
        # key为stopOrderID，value为stopOrder对象
        self.stopOrderDict = {}             # 停止单撤销后不会从本字典中删除
        self.workingStopOrderDict = {}      # 停止单撤销后会从本字典中删除

        #-----------------------------------------------------------------------
        self.timeList=[]
        self.key_name_list=[]               # 假如有多个data传进来,数据键值
        self.kwargs=None                    # 从数据库读取的所有数据

        self.bar_cmp=None                   # 其他周期与基础bar的周期比值，比如base_bar是1分钟，其他周期为5分钟，那么该变量为[1,5]，第一个为单位一定为1
                                            # {'type':倍数,'type2':倍数}
        self.bar_cmp_fore=None              # 对应base_bar的前n根，{'type':前n根，...}

    # --------------------------------------------------------------------------
    # 把类broker作为主引擎的一个属性，那么就可以通过BacktestingEngine.broker调用broker类
    def getBroker(self):
        return self.__broker

    broker=property(getBroker)

    # --------------------------------------------------------------------------
    # 引擎相关的设置
    def setStartTime(self,start_time_string):
        self.start_time=start_time_string

    def setEndTime(self,end_time_string):
        self.end_time=end_time_string

    def setSlippage(self, slippage):
        '''设置滑点'''
        self.slippage = slippage

    def setSize(self, size):
        """设置合约大小"""
        self.size = size 

    def setRate(self, rate):
        """设置佣金比例"""
        self.rate = rate

    def setInitCapital(self):
        """保存初始金额"""
        self.initcapital=self.broker.capital
        self.capital=self.initcapital
        self.cash=self.initcapital

    def setMode(self):
        '''设置模式,bar还是tick,默认是Bar'''
        if self.mode=='BAR':
            print("这里要实例化bar结构的类")
        else:
            print("这里要实例化TICK结构的类")

    # --------------------------------------------------------------------------
    # 从外部包加载到引擎
    # 添加数据,原参数为(bar_cmp=None,**kwargs),现直接传数据字典即可，效果一样
    def addMulData(self,kwargs,bar_cmp,q):
        '''多品种数据输入,q是数据队列'''
        self.bar_cmp=bar_cmp
        self.q=q

    def addData(self,kwargs,bar_cmp=None):
        '''传入数据，传进来就是已经处理好的数据,
        kwargs={'base':data1,'type2':data2,...},bar_cmp={'base':1,'5min':5,'1h':60,..}'''
        if kwargs is None:
            raise ValueError("没有传进data")

        self.bar_cmp=bar_cmp
        self.kwargs=kwargs
        
        self.K_line_data=self.kwargs['base']
        self.all_close_price=list()

        if len(kwargs)>1:
            for j in kwargs:
                self.key_name_list.append(j)   # 保存键值,其他周期的数据从这个列表调

            for i in self.K_line_data:
                self.all_close_price.append(i['close'])
                self.timeList.append(i['time'])
        else:
            for i in self.K_line_data:
                self.all_close_price.append(i['close'])
                self.timeList.append(i['time'])

    def addStrategy(self,Strategy,setting):
        '''加载策略'''
        self.strategy=Strategy(self,setting)    # 最核心的一行代码
        
    def addAnalyzer(self,analyzer):
        '''添加分析师'''
        self.analyzer=analyzer()

    def addIndicator(self,indicator):
        '''添加指示器'''
        self.indicator=indicator()

    # --------------------------------------------------------------------------
    # 回测结果，计算与展示
    # 出图
    def plot(self,type_=None):
        # type为None,调用matplotlib,type为echarts调用pyecharts,type为bokeh调用bokeh
        if not type_:
            from hashakisea.ui.plotter.matplot import Matplot
            import datetime
            import matplotlib.dates as mpd
            str2date = lambda x: mpd.date2num(datetime.datetime.strptime(x, '%Y%m%d').date())
            data_ = []
            for i in self.K_line_data:
                data_.append([i['date'], i['open'], i['high'], i['low'], i['close']])
                quotes = [[str2date(d[0])] + [float(v) for v in d[1:]] for d in data_]
            m = Matplot()
            m.kline(quotes)

        elif type_ == 'bokeh':
            from hashakisea.ui.plotter.bokehMethod import Bokeh
            import pandas as pd
            df_ = pd.DataFrame(self.K_line_data)
            b = Bokeh()
            #b.kline(df_)

        elif type_=='echart':
            # 结果图片会保存在运行文件目录，就是backtestEngine所在目录
            from hashakisea.ui.plotter.echart import Echarts
            time_=[]
            K_bar=[]
            for i in self.K_line_data:
                time_.append(i['time'])
                K_bar.append([i['open'],i['close'],i['low'],i['high']])
            cha=Echarts()
            cha.kLine('hashaki',time_,K_bar)

    def calculateResult(self):
        pass

    # ---------------------------------------------------------------------------
    # 辅助函数
    def roundToPriceTick(self, price, priceTick=None):
        """取整价格到合约最小价格变动"""
        if not priceTick:
            priceTick = self.priceTick
        if not priceTick:
            return price

        newPrice = round(price/priceTick, 0) * priceTick
        return newPrice

    # ---------------------------------------------------------------------------
    # 回测计算,撮合，回测引擎的大部分计算都在这里
    def crossStopOrder(self):
        """基于最新数据撮合停止单"""
        # 先确定会撮合成交的价格，这里和限价单规则相反
        if self.mode == 'BAR':
            buyCrossPrice = self.bar.high    # 若买入方向停止单价格低于该价格，则会成交
            sellCrossPrice = self.bar.low    # 若卖出方向限价单价格高于该价格，则会成交
            bestCrossPrice = self.bar.open   # 最优成交价，买入停止单不能低于，卖出停止单不能高于
            target_symbol = self.bar.target_symbol

        else:
            '''
            buyCrossPrice = self.tick.lastPrice
            sellCrossPrice = self.tick.lastPrice
            bestCrossPrice = self.tick.lastPrice
            vtSymbol = self.tick.vtSymbol
            '''
            pass
        # 遍历停止单字典中的所有停止单
        for stopOrderID, so in list(self.workingStopOrderDict.items()):     # self.workingStopOrderDict.items()转换成list结构，后下面注释能完成
           
            buyCross = so.direction == DIRECTION_LONG and so.price <= buyCrossPrice #and vtSymbol.lower() == so.vtSymbol.lower()
            sellCross = so.direction == DIRECTION_SHORT and so.price >= sellCrossPrice #and vtSymbol.lower() == so.vtSymbol.lower()

            # 如果发生了成交 
            if buyCross or sellCross:
                # 推送成交数据
                self.tradeCount += 1            # 成交编号自增1

                # 添加result,如果没有持仓，则说明是一个新的交易对的开始
                if self.pos==0:
                    self.result.open_time=self.dt

                tradeID = str(self.tradeCount)
                trade = tradeClass()
                trade.target_symbol = so.target_symbol
                trade.tradeID = tradeID
                trade.HaTradeID = tradeID
                
                if buyCross:
                    trade.price = max(bestCrossPrice, so.price)
                    self.marketMoney += so.volume * trade.price
                    self.cash-=so.volume * trade.price
                    self.pos+=so.volume

                    # 为了计算交易对结果
                    self.result.open_base.append(trade.price)
                    self.result.open_vol.append(so.volume)
                else:
                    trade.price = min(bestCrossPrice, so.price)
                    self.marketMoney -= so.volume * trade.price
                    self.cash+=so.volume * trade.price
                    self.pos-=so.volume

                    self.result.close_base.append(trade.price)
                    self.result.close_vol.append(so.volume)
                    # 卖出后仓位为0,说明是一个交易对的结束
                    if self.pos==0:
                        # TODO:为了防止报错在这里加了slippage，commis，pnl，以后在引擎中赋值即可删掉
                        self.result.commis=0.01
                        self.result.pnl=10.0
                        self.result.slippage=0.1

                        self.result.clsoe_time=self.dt
                        self.resultList.append(self.result)
                        self.restartResult(self.result)
                
                self.limitOrderCount += 1
                orderID = str(self.limitOrderCount)
                trade.orderID = orderID
                trade.HaOrderID = orderID
                
                trade.direction = so.direction
                trade.offset = so.offset
                trade.volume = so.volume
                trade.tradeTime = str(self.dt)
                trade.dt = self.dt
                self.strategy.onTrade(trade)
                
                self.tradeDict[tradeID] = trade
                
                # 推送委托数据
                so.status = STOPORDER_TRIGGERED

                order = orderClass()
                order.target_symbol = so.target_symbol
                order.symbol = so.target_symbol
                order.orderID = orderID
                order.HaOrderID = orderID
                order.direction = so.direction
                order.offset = so.offset
                order.price = so.price
                order.totalVolume = so.volume
                order.tradedVolume = so.volume
                order.status = STATUS_ALLTRADED
                order.orderTime = trade.tradeTime
                self.strategy.onOrder(order)
                
                self.limitOrderDict[orderID] = order
                
                # 从字典中删除该限价单
                if stopOrderID in self.workingStopOrderDict:
                    del self.workingStopOrderDict[stopOrderID]

    def crossLimitOrder(self):
        """基于最新数据撮合限价单"""
        if self.mode=='BAR':
            buyCrossPrice = self.roundToPriceTick(self.bar.low) + self.priceTick        # 若买入方向限价单价格高于该价格，则会成交
            sellCrossPrice = self.roundToPriceTick(self.bar.high) - self.priceTick      # 若卖出方向限价单价格低于该价格，则会成交
            buyBestCrossPrice = self.roundToPriceTick(self.bar.open) + self.priceTick   # 在当前时间点前发出的买入委托可能的最优成交价
            sellBestCrossPrice = self.roundToPriceTick(self.bar.open) - self.priceTick  # 在当前时间点前发出的卖出委托可能的最优成交价
            target_symbol = self.bar.target_symbol='eos'
        else:
            '''
            buyCrossPrice = self.tick.askPrice1
            sellCrossPrice = self.tick.bidPrice1
            buyBestCrossPrice = self.tick.askPrice1
            sellBestCrossPrice = self.tick.bidPrice1
            Symbol = self.tick.Symbol
            '''
            pass

        # 遍历限价单字典中的所有限价单
        for orderID, order in list(self.workingLimitOrderDict.items()):
            # 判断是否会成交
            buyCross = order.direction == DIRECTION_LONG and order.price >= buyCrossPrice #and vtSymbol.lower() == order.vtSymbol.lower()
            sellCross = order.direction == DIRECTION_SHORT and order.price <= sellCrossPrice #and vtSymbol.lower() == order.vtSymbol.lower()

            # 如果发生了成交
            if buyCross or sellCross:
                # 推送成交数据
                self.tradeCount += 1            # 成交编号自增1

                tradeID = str(self.tradeCount)
                trade = tradeClass()
                trade.target_symbol = order.target_symbol
                trade.tradeID = tradeID
                trade.HaTradeID = tradeID
                trade.orderID = order.orderID
                trade.HaOrderID = order.orderID
                trade.direction = order.direction
                #trade.offset = order.offset

                # 添加result,如果没有持仓，则说明是一个新的交易对的开始
                if self.pos==0:
                    self.result.open_time=self.dt

                # 以买入为例：
                # 1. 假设当根K线的OHLC分别为：100, 125, 90, 110
                # 2. 假设在上一根K线结束(也是当前K线开始)的时刻，策略发出的委托为限价105
                # 3. 则在实际中的成交价会是100而不是105，因为委托发出时市场的最优价格是100
                if buyCross:
                    trade.price = min(order.price, buyBestCrossPrice)
                    self.marketMoney += order.totalVolume * trade.price
                    self.cash-=order.totalVolume * trade.price
                    self.pos+=order.totalVolume

                    # 为了计算交易对结果
                    self.result.open_base.append(trade.price)
                    self.result.open_vol.append(order.totalVolume)

                else:
                    trade.price = max(order.price, sellBestCrossPrice)
                    self.marketMoney -= order.totalVolume * trade.price
                    self.cash+=order.totalVolume * trade.price
                    self.pos-=order.totalVolume

                    self.result.close_base.append(trade.price)
                    self.result.close_vol.append(order.totalVolume)
                    # 卖出后仓位为0,说明是一个交易对的结束
                    if self.pos==0:
                        # TODO:为了防止报错在这里加了slippage，commis，pnl，以后在引擎中赋值即可删掉
                        self.result.commis=0.01
                        self.result.pnl=10.0
                        self.result.slippage=0.1

                        self.result.clsoe_time=self.dt
                        self.resultList.append(self.result)
                        self.restartResult(self.result)

                trade.volume = order.totalVolume
                trade.tradeTime = str(self.dt)
                trade.dt = self.dt
                self.strategy.onTrade(trade)
                self.tradeDict[tradeID] = trade

                # 推送委托数据
                order.tradedVolume = order.totalVolume
                order.status = u'全部成交'

                self.strategy.onOrder(order)

                # 从字典中删除该限价单
                try:
                    del self.workingLimitOrderDict[orderID]
                except Exception as ex:
                    #self.writeCtaError(u'crossLimitOrder exception:{},{}'.format(str(ex), traceback.format_exc()))
                    print("crossLimitOrder exception",ex)

    def sendOrder(self,taget_symbol,type_,price,volume,strategy_name):
        '''broker处理好之后，让这个函数处理,0为buy，1为sell，2为撤单'''
        self.limitOrderCount += 1
        orderID = str(self.limitOrderCount)

        order = orderClass()
        order.taget_symbol= taget_symbol
        order.price = self.roundToPriceTick(price)
        order.status = STATUS_NOTTRADED     # 刚提交尚未成交
        order.orderID = orderID
        order.HaOrderID = orderID
        order.orderTime = str(self.dt)

        if type_== CTAORDER_BUY:
            order.direction = DIRECTION_LONG
            order.totalVolume = volume

        elif type_== CTAORDER_SELL:
            order.direction = DIRECTION_SHORT
            order.totalVolume = volume

        elif type_== CTAORDER_SHORT:
            order.direction = DIRECTION_SHORT
            order.totalVolume = volume

        elif type_== CTAORDER_COVER:
            order.direction = DIRECTION_LONG
            order.totalVolume = volume

        else:
            raise ValueError("不是正确的type：{} 0为buy，1为sell，2为撤单".format(type_))

        key = u'{}.{}'.format(strategy_name,orderID)
        # 保存到限价单字典中
        self.workingLimitOrderDict[key] = order
        self.limitOrderDict[key] = order
        return key
    
    def sendStopOrder(self, taget_symbol, orderType, price, volume, strategy):
        """发停止单（本地实现）"""
        self.stopOrderCount += 1
        stopOrderID ='stopOrder' + str(self.stopOrderCount)

        so = StopOrder()
        so.target_symbol = taget_symbol
        so.price = self.roundToPriceTick(price)
        so.volume = volume
        so.strategy = strategy
        so.stopOrderID = stopOrderID
        so.status = '等待中'

        if orderType == CTAORDER_BUY:
            so.direction = DIRECTION_LONG
            so.offset = OFFSET_OPEN
        elif orderType == CTAORDER_SELL:
            so.direction = DIRECTION_SHORT
            so.offset = OFFSET_CLOSE
        elif orderType == CTAORDER_SHORT:
            so.direction = DIRECTION_SHORT
            so.offset = OFFSET_OPEN
        elif orderType == CTAORDER_COVER:
            so.direction = DIRECTION_LONG
            so.offset = OFFSET_CLOSE           

        # 保存stopOrder对象到字典中
        self.stopOrderDict[stopOrderID] = so
        self.workingStopOrderDict[stopOrderID] = so

        return stopOrderID

    def cancelOrder(self,HaOrderID=None):
        '''取消订单'''
        # 撤单
        if HaOrderID in self.workingLimitOrderDict:
            order = self.workingLimitOrderDict[HaOrderID]
            order.status = STATUS_CANCELLED
            order.cancelTime = str(self.dt)
            del self.workingLimitOrderDict[HaOrderID]
        # 撤销所有单，TODO：做成按品种的全撤销
        # Symbol参数:指定合约的撤单；
        # OFFSET参数:指定Offset的撤单,缺省不填写时，为所有
        else:
            for OrderID_ in self.workingLimitOrderDict.keys():
                order = self.workingLimitOrderDict[OrderID_]
                order.status = STATUS_CANCELLED
                order.cancelTime = str(self.dt)
                del self.workingLimitOrderDict[OrderID_]

    def newBar(self,bar):
        '''K线处理'''
        self.bar=bar
        self.crossLimitOrder()
        self.crossStopOrder()
        self.strategy.next(bar)
        self.last_bar=bar

    def newBars(self,bars):
        '''多品种K线处理'''
        for i in list(bars.values()):
            self.bar=i
            self.crossLimitOrder()
            self.crossStopOrder()
            self.last_bar=i
        self.strategy.next(bars)

    def calculateAllWaves(self):
        '''计算市场波动，暂时这么算'''
        # 方法一：[(上一个close-该次open)/上一个close,...]
        # 方法二：[close,...]
        self.market_money_wave=[]
        for i in range(len(self.all_close_price)):
            if i == 0.0:
                self.market_money_wave.append(1.0)
            else:
                tamp=1.0+(self.all_close_price[i]-self.all_close_price[i-1])/self.all_close_price[i-1]
                self.market_money_wave.append(tamp)

    def calculateMarketMoney(self,market_money,legth=1):
        '''计算钱在市场的变化，legth=1，说明是按每根bar来计算的,默认是1'''
        tamp=market_money*self.market_money_wave[self.bar_num]
        self.marketValueList.append(tamp)
        return tamp

    def calculatePos(self):
        '''计算市场仓位'''
        self.posList.append(self.pos)

    def calculateBarCMP(self,name):
        '''用于多周期计算其他周期从哪里开始往回切片，切片在策略函数中,name是第几个周期'''
        if not self.bar_cmp:
            raise Exception("只有一个周期，没法使用多周期功能")
        
        assert type(name) is str

        return floor(self.count_tick/self.bar_cmp[name]+self.bar_cmp_fore)

    def restartResult(self,resultObject):
        '''清洗result对象'''
        del resultObject.open_time
        del resultObject.clsoe_time
        resultObject.open_base.clear()
        resultObject.close_base.clear()
        resultObject.open_vol.clear()
        resultObject.close_vol.clear()
        del resultObject.slippage
        del resultObject.commis
        del resultObject.pnl
        #print("清洗完毕")

    # --------------------------------------------------------------------------
    # 开始回测
    def run(self,spot=False,mul=False):
        '''开始运行回测'''
        self.setInitCapital()
        self.setMode()
        if mul:
            data=self.q.get()                      # data={'A':dataA,'B':dataB,...},dataA=[{'time':'20181114,'volume':1.1,...},{...},...]
            steps=len(list(data.items())[0][1])    # 获得每个品种都有多少条数据
            for i in range(steps):
                bars={}
                for j in data:
                    bar=barClass()
                    bar.__dict__=data[j][i]
                    bars[j]=bar
                # print(list(bars.values())[0].__dict__) 检查bar里都有什么变量
                
                self.newBars(bars)
                self.dt=list(bars.values())[0].time  # bars={'A':bar1,'B':bar2,..}  bar1={'time':'20181114,'volume':1.1,...}
                self.count_tick+=1
                self.calculatePos()

        '''
        self.calculateAllWaves()
        if self.strategy:            # 有策略才能跑策略啊
            self.strategy.start()
            for i in self.K_line_data:
                data=barClass()
                data.__dict__=i
                self.newBar(data)
                self.dt=i['time']
                self.count_tick+=1
                self.calculatePos()

                tamp=self.calculateMarketMoney(self.marketMoney)
                self.capitalList.append(self.cash+tamp)
                self.cashList.append(self.cash)
                self.bar_num+=1
        '''

class BacktestingEngine2:
    def __init__(self):
        self.__broker=BacktestBroker(self)
        self.mode='BAR'             # 默认是bar模式
        self.strategy=None          # 以防策略为空，后面作判断使用
        self.analyzer=None          # 以防为空被调用，后面作判断
        self.indicator=None         # 同上

        # 在回测前，用户需要给的设置
        self.initcapital=None       # 初始金额 初始金额在broker中实现
        self.capital = None         # 总资产
        self.cash = None            # 现金
        self.start_time=''          # 回测数据开始数据 20181022
        self.end_time=''            # 回测数据截至时间 20181022
        self.slippage = 0           # 回测时假设的滑点
        self.rate = 0               # 回测时假设的佣金比例（适用于百分比佣金）
        self.size = 1               # 合约大小，默认为1        
        self.priceTick = 0.001     # 价格最小变动

        # -----------------------------------------------------------------------
        self.count_tick=0                   # 第几根base_bar
        self.marketMoney=defaultdict(float)        # 持仓的基础货币
        self.limitOrderCount = defaultdict(int)
        self.stopOrderCount=defaultdict(int)
        self.bar_num=0                      # bar的总根数,用于算市场波动/hashaki
        self.dt=''                          # 现在时间
        self.tradeCount = defaultdict(int)                 # 成交编号
        self.pos=defaultdict(float)
        self.deposit=defaultdict(float)

        self.tradeDict = defaultdict(list)      # 成交字典
        self.longPosition ={}              # 多单持仓
        self.shortPosition = {}             # 空单持仓
        self.limitOrderDict = {}         # 限价单字典
        self.workingLimitOrderDict = defaultdict(dict)  # 活动限价单字典，用于进行撮合用
        self.marketValueList ={}    # 钱在市场,现货可用
        self.cashList=list()             # 口袋里的钱
        self.capitalList=list()          # 总资产
        self.posList=defaultdict(deque)
        self.depositList=defaultdict(deque)
        self.result=ResultClass()        # {'开仓时间':'','平仓时间':'','买进金额':[],...}
        self.resultList=defaultdict(deque)

        # 本地停止单字典
        # key为stopOrderID，value为stopOrder对象
        self.stopOrderDict = {}             # 停止单撤销后不会从本字典中删除
        self.workingStopOrderDict =defaultdict(dict)      # 停止单撤销后会从本字典中删除

        #-----------------------------------------------------------------------
        self.timeList=[]
        self.key_name_list=[]               # 假如有多个data传进来,数据键值
        self.kwargs=None                    # 从数据库读取的所有数据

        self.bar_cmp=None                   # 其他周期与基础bar的周期比值，比如base_bar是1分钟，其他周期为5分钟，那么该变量为[1,5]，第一个为单位一定为1
                                            # {'type':倍数,'type2':倍数}
        self.bar_cmp_fore=None              # 对应base_bar的前n根，{'type':前n根，...}

    # --------------------------------------------------------------------------
    # 把类broker作为主引擎的一个属性，那么就可以通过BacktestingEngine.broker调用broker类
    def getBroker(self):
        return self.__broker

    broker=property(getBroker)

    # --------------------------------------------------------------------------
    # 引擎相关的设置
    def setStartTime(self,start_time_string):
        self.start_time=start_time_string

    def setEndTime(self,end_time_string):
        self.end_time=end_time_string

    def setSlippage(self, slippage):
        '''设置滑点'''
        self.slippage = slippage

    def setSize(self, size):
        """设置合约大小"""
        self.size = size 

    def setRate(self, rate):
        """设置佣金比例"""
        self.rate = rate

    def setInitCapital(self):
        """保存初始金额"""
        self.initcapital=self.broker.capital
        self.capital=self.initcapital
        self.cash=self.initcapital

    # --------------------------------------------------------------------------
    # 从外部包加载到引擎
    # 添加数据,原参数为(bar_cmp=None,**kwargs),现直接传数据字典即可，效果一样
    def addMulData(self,kwargs,bar_cmp,q):
        '''多品种数据输入,q是数据队列,每条数据的形式：{'time':'20181122','open':1.0.'high':2.0.'low':1.0,'close':2.0},在外面才有target_symbol '''
        self.bar_cmp=bar_cmp
        self.q=q

    def addStrategy(self,Strategy,setting):
        '''加载策略'''
        self.strategy=Strategy(self,setting)    # 最核心的一行代码
        
    def addAnalyzer(self,analyzer):
        '''添加分析师'''
        self.analyzer=analyzer()

    def addIndicator(self,indicator):
        '''添加指示器'''
        self.indicator=indicator()

    # --------------------------------------------------------------------------
    # 辅助函数
    def roundToPriceTick(self, price, priceTick=None):
        """取整价格到合约最小价格变动"""
        if not priceTick:
            priceTick = self.priceTick
        if not priceTick:
            return price

        newPrice = round(price/priceTick, 0) * priceTick
        return newPrice

    # ---------------------------------------------------------------------------
    # 回测计算,撮合，回测引擎的大部分计算都在这里
    def crossStopOrder(self):
        """基于最新数据撮合停止单"""
        # 先确定会撮合成交的价格，这里和限价单规则相反
        buyCrossPrice = self.bar.high    # 若买入方向停止单价格低于该价格，则会成交
        sellCrossPrice = self.bar.low    # 若卖出方向限价单价格高于该价格，则会成交
        bestCrossPrice = self.bar.open   # 最优成交价，买入停止单不能低于，卖出停止单不能高于
        target_symbol = self.bar.target_symbol

        if not self.workingStopOrderDict:
            return
        # 遍历停止单字典中的所有停止单
        for stopOrderID, so in list(self.workingStopOrderDict[target_symbol].items()):     # self.workingStopOrderDict.items()转换成list结构，后下面注释能完成
           
            buyCross = so.direction == DIRECTION_LONG and so.price <= buyCrossPrice #and vtSymbol.lower() == so.vtSymbol.lower()
            sellCross = so.direction == DIRECTION_SHORT and so.price >= sellCrossPrice #and vtSymbol.lower() == so.vtSymbol.lower()

            # 如果发生了成交 
            if buyCross or sellCross:
                # 推送成交数据
                self.tradeCount[target_symbol] += 1            # 成交编号自增1

                # 添加result,如果没有持仓，则说明是一个新的交易对的开始
                if self.pos[target_symbol]==0:
                    self.result.open_time=self.dt

                tradeID = str(self.tradeCount)
                trade = tradeClass()
                trade.target_symbol = so.target_symbol
                trade.tradeID = tradeID
                trade.HaTradeID = tradeID
                
                if buyCross:
                    trade.price = max(bestCrossPrice, so.price)
                    self.marketMoney[target_symbol] += so.volume * trade.price
                    self.cash-=so.volume * trade.price
                    self.pos[target_symbol]+=so.volume

                    # 为了计算交易对结果
                    self.result.open_base.append(trade.price)
                    self.result.open_vol.append(so.volume)
                else:
                    trade.price = min(bestCrossPrice, so.price)
                    self.marketMoney[target_symbol] -= so.volume * trade.price
                    self.cash+=so.volume * trade.price
                    self.pos[target_symbol]-=so.volume

                    self.result.close_base.append(trade.price)
                    self.result.close_vol.append(so.volume)
                    # 卖出后仓位为0,说明是一个交易对的结束
                    if self.pos[target_symbol]==0:
                        # TODO:为了防止报错在这里加了slippage，commis，pnl，以后在引擎中赋值即可删掉
                        self.result.commis=0.01
                        self.result.pnl=10.0
                        self.result.slippage=0.1

                        self.result.clsoe_time=self.dt
                        self.resultList[target_symbol].append(self.result)
                        self.restartResult(self.result)
                
                self.limitOrderCount[target_symbol] += 1
                orderID = str(self.limitOrderCount[target_symbol])
                trade.orderID = orderID
                trade.HaOrderID = orderID
                
                trade.direction = so.direction
                trade.offset = so.offset
                trade.volume = so.volume
                trade.tradeTime = str(self.dt)
                trade.dt = self.dt
                self.strategy.onTrade(trade)
                
                self.tradeDict[target_symbol].append({tradeID:trade})
                
                # 推送委托数据
                so.status = STOPORDER_TRIGGERED

                order = orderClass()
                order.target_symbol = so.target_symbol
                order.symbol = so.target_symbol
                order.orderID = orderID
                order.HaOrderID = orderID
                order.direction = so.direction
                order.offset = so.offset
                order.price = so.price
                order.totalVolume = so.volume
                order.tradedVolume = so.volume
                order.status = STATUS_ALLTRADED
                order.orderTime = trade.tradeTime
                self.strategy.onOrder(order)
                
                self.limitOrderDict[target_symbol][orderID] = order
                
                # 从字典中删除该限价单
                if stopOrderID in self.workingStopOrderDict[target_symbol]:
                    del self.workingStopOrderDict[target_symbol][stopOrderID]

    def crossLimitOrder(self):
        """基于最新数据撮合限价单,传进来的是一个bar，同一个时间的不同bar循环传进来"""
        buyCrossPrice = self.roundToPriceTick(self.bar.low) + self.priceTick        # 若买入方向限价单价格高于该价格，则会成交
        sellCrossPrice = self.roundToPriceTick(self.bar.high) - self.priceTick      # 若卖出方向限价单价格低于该价格，则会成交
        buyBestCrossPrice = self.roundToPriceTick(self.bar.open) + self.priceTick   # 在当前时间点前发出的买入委托可能的最优成交价
        sellBestCrossPrice = self.roundToPriceTick(self.bar.open) - self.priceTick  # 在当前时间点前发出的卖出委托可能的最优成交价
        target_symbol = self.bar.target_symbol

        # 当下单表为空的时候，返回
        if not self.workingLimitOrderDict:
            return

        # 遍历限价单字典中的所有限价单,orderID是key值，order是value值
        for orderID, order in list(self.workingLimitOrderDict[target_symbol].items()):
            # 判断是否会成交
            buyCross = order.direction == DIRECTION_LONG and order.price >= buyCrossPrice
            sellCross = order.direction == DIRECTION_SHORT and order.price <= sellCrossPrice

            # 如果发生了成交
            if buyCross or sellCross:
                # 推送成交数据
                self.tradeCount[target_symbol] += 1            # 成交编号自增1

                tradeID = str(self.tradeCount[target_symbol])
                trade = tradeClass()
                trade.target_symbol = order.target_symbol
                trade.tradeID = tradeID
                trade.HaTradeID = tradeID
                trade.orderID = order.orderID
                trade.HaOrderID = order.orderID
                trade.direction = order.direction
                #trade.offset = order.offset

                # 添加result,如果没有持仓，则说明是一个新的交易对的开始
                if self.pos[target_symbol]==0:
                    self.result.open_time=self.dt

                # 以买入为例：
                # 1. 假设当根K线的OHLC分别为：100, 125, 90, 110
                # 2. 假设在上一根K线结束(也是当前K线开始)的时刻，策略发出的委托为限价105
                # 3. 则在实际中的成交价会是100而不是105，因为委托发出时市场的最优价格是100
                if buyCross:
                    trade.price = min(order.price, buyBestCrossPrice)
                    self.marketMoney[target_symbol] += order.totalVolume * trade.price
                    self.cash-=order.totalVolume * trade.price
                    self.pos[target_symbol]+=order.totalVolume

                    # 为了计算交易对结果
                    self.result.open_base.append(trade.price)
                    self.result.open_vol.append(order.totalVolume)

                else:
                    trade.price = max(order.price, sellBestCrossPrice)
                    self.marketMoney[target_symbol] -= order.totalVolume * trade.price
                    self.cash+=order.totalVolume * trade.price
                    self.pos[target_symbol]-=order.totalVolume

                    self.result.close_base.append(trade.price)
                    self.result.close_vol.append(order.totalVolume)
                    # 卖出后仓位为0,说明是一个交易对的结束
                    if self.pos[target_symbol]==0:
                        # TODO:为了防止报错在这里加了slippage，commis，pnl，以后在引擎中赋值即可删掉
                        self.result.commis=0.01
                        self.result.pnl=10.0
                        self.result.slippage=0.1

                        self.result.clsoe_time=self.dt
                        self.resultList[target_symbol].append(self.result)
                        self.restartResult(self.result)

                trade.volume = order.totalVolume
                trade.tradeTime = str(self.dt)
                trade.dt = self.dt
                self.strategy.onTrade(trade)
                self.tradeDict[target_symbol].append({tradeID:trade})

                # 推送委托数据
                order.tradedVolume = order.totalVolume
                order.status = u'全部成交'

                self.strategy.onOrder(order)

                # 从字典中删除该限价单
                try:
                    del self.workingLimitOrderDict[target_symbol][orderID]
                except Exception as ex:
                    #self.writeCtaError(u'crossLimitOrder exception:{},{}'.format(str(ex), traceback.format_exc()))
                    print("crossLimitOrder exception",ex)

    def sendOrder(self,target_symbol,type_,price,volume,strategy_name):
        '''broker处理好之后，让这个函数处理,0为buy，1为sell，2为撤单'''
        self.limitOrderCount[target_symbol] += 1
        orderID = str(self.limitOrderCount[target_symbol])

        order = orderClass()
        order.target_symbol= target_symbol
        order.price = self.roundToPriceTick(price)
        order.status = STATUS_NOTTRADED     # 刚提交尚未成交
        order.orderID = orderID
        order.HaOrderID = orderID
        order.orderTime = str(self.dt)

        if type_== CTAORDER_BUY:
            order.direction = DIRECTION_LONG
            order.totalVolume = volume

        elif type_== CTAORDER_SELL:
            order.direction = DIRECTION_SHORT
            order.totalVolume = volume

        elif type_== CTAORDER_SHORT:
            order.direction = DIRECTION_SHORT
            order.totalVolume = volume

        elif type_== CTAORDER_COVER:
            order.direction = DIRECTION_LONG
            order.totalVolume = volume

        else:
            raise ValueError("不是正确的type：{} 0为buy，1为sell，2为撤单".format(type_))

        key = u'{}'.format(orderID)
        # 保存到限价单字典中
        self.workingLimitOrderDict[target_symbol] = {key:order}
        self.limitOrderDict[target_symbol] = {key:order}
        return key
    
    def sendStopOrder(self, target_symbol, orderType, price, volume, strategy):
        """发停止单（本地实现）"""
        self.stopOrderCount[target_symbol] += 1
        stopOrderID ='stopOrder' + str(self.stopOrderCount[target_symbol])

        so = StopOrder()
        so.target_symbol = target_symbol
        so.price = self.roundToPriceTick(price)
        so.volume = volume
        so.strategy = strategy
        so.stopOrderID = stopOrderID
        so.status = '等待中'

        if orderType == CTAORDER_BUY:
            so.direction = DIRECTION_LONG
            so.offset = OFFSET_OPEN
        elif orderType == CTAORDER_SELL:
            so.direction = DIRECTION_SHORT
            so.offset = OFFSET_CLOSE
        elif orderType == CTAORDER_SHORT:
            so.direction = DIRECTION_SHORT
            so.offset = OFFSET_OPEN
        elif orderType == CTAORDER_COVER:
            so.direction = DIRECTION_LONG
            so.offset = OFFSET_CLOSE           

        # 保存stopOrder对象到字典中
        self.stopOrderDict[target_symbol] = {stopOrderID: so}
        self.workingStopOrderDict[target_symbol] = {stopOrderID: so}

        return stopOrderID

    def cancelOrder(self,target_symbol,OrderID=None):
        '''取消订单'''
        # 撤单
        if OrderID in self.workingLimitOrderDict[target_symbol]:
            order = self.workingLimitOrderDict[target_symbol][OrderID]
            order.status = STATUS_CANCELLED
            order.cancelTime = str(self.dt)
            del self.workingLimitOrderDict[target_symbol][OrderID]
        # 撤销所有单，TODO：做成按品种的全撤销
        # Symbol参数:指定合约的撤单；
        # OFFSET参数:指定Offset的撤单,缺省不填写时，为所有
        else:
            for target in self.workingLimitOrderDict.keys():
                for OrderID_ in self.workingLimitOrderDict[target].keys():
                    order = self.workingLimitOrderDict[target][OrderID_]
                    order.status = STATUS_CANCELLED
                    order.cancelTime = str(self.dt)
                    del self.workingLimitOrderDict[target][OrderID_]

    def newBar(self,bar):
        '''K线处理'''
        self.bar=bar
        self.crossLimitOrder()
        self.crossStopOrder()
        self.strategy.next(bar)
        self.last_bar=bar

    def newBars(self,bars):
        '''多品种K线处理，bars的结构为{'target_symbol1':bar1,'target_symbol2':bar2,...} '''
        for i in list(bars.values()):
            self.bar=i
            self.crossLimitOrder()
            self.crossStopOrder()
            self.last_bar=i
        self.strategy.next(bars)

    
    def calculatePos(self,target_symbol):
        '''计算市场仓位'''
        # note:这里要注意，在仓位列表添加没问题，要注意在调用这个函数之前，这个仓位pos必须已经完成了赋值（也就是经过了一轮撮合运算）
        self.posList[target_symbol].append(self.pos[target_symbol])

    def calculateBarCMP(self,name):
        '''用于多周期计算其他周期从哪里开始往回切片，切片在策略函数中,name是第几个周期'''
        if not self.bar_cmp:
            raise Exception("只有一个周期，没法使用多周期功能")

        assert type(name) is str

        return floor(self.count_tick/self.bar_cmp[name]+self.bar_cmp_fore)

    def restartResult(self,resultObject):
        '''清洗result对象'''
        del resultObject.open_time
        del resultObject.clsoe_time
        resultObject.open_base.clear()
        resultObject.close_base.clear()
        resultObject.open_vol.clear()
        resultObject.close_vol.clear()
        del resultObject.slippage
        del resultObject.commis
        del resultObject.pnl
        #print("清洗完毕")

    # --------------------------------------------------------------------------
    # 开始回测
    def run(self,spot=False,mul=False):
        '''开始运行回测'''
        self.setInitCapital()
        if mul:
            data=self.q.get()                      # data={'A':dataA,'B':dataB,...},dataA=[{'time':'20181114,'volume':1.1,...},{...},...]

            temp=[]
            for j in list(data.values())[0]:   # 用于判断最早的时间,使用的是时间对齐的方式推送bar
                temp.append(j['time'])
            temp_time=min(temp)     # 所有数据中时间最早的那个时间

            for l in list(data.values())[-1]:
                temp.append(l['time'])
            final_time=max(temp)

            bars={}

            while temp_time<final_time:
                next_time_list=[]
                for i in data:
                    bar=barClass()

                    try:
                        temp_data=data[i][self.count_tick]   # 若数据有长有短，那么同一个索引肯定是超列表长度的
                    except IndexError:
                        temp_data={'open':0,'high':0,'low':0,'close':0,'volume':0,'time':temp_time}   # 时光锁定在该组数据的最后一个

                    if temp_data['time']>temp_time:
                        # 伪造一个空bar
                        bar.__dict__={'open':0,'high':0,'low':0,'close':0,'volume':0,'time':temp_time}  # 数据时间比最早时间晚，说明该时间下，数据是没有的，所以传一个空bar
                    else:
                        bar.__dict__=temp_data       # 这是把时间，成交量，开高低收数据传进bar
                    bar.target_symbol=i           # 是什么品种的bar
                    bars[i]=bar
                    next_time_list.append(temp_data['time'])   # 把大的时间放进列表，用于更新下一档时间
                    # print(list(bars.values())[0].__dict__) 检查bar里都有什么变量

                self.newBars(bars)
                # 这里的self.dr其实时上一个bar的时间
                self.dt=temp_time                         # bars={'A':bar1,'B':bar2,..}  bar1={'time':'20181114,'volume':1.1,...}
                self.count_tick+=1
                temp_time=max(next_time_list)    # 选出最大的那个时间作为时间更新

                for target_symbol in list(bars.keys()):
                    self.calculatePos(target_symbol)
            # 这里的分析只接受单一品种，所以直接把单一品种传进去分析即可
            if self.analyzer:
                for k in self.tradeDict.values():     # k为单个品种的成交列表字典
                    self.analyzer.set_data(k,)
                    self.analyzer.set_parameter(self.rate, self.slippage, self.size, 0, self.last_bar, self.initcapital)
                    self.analyzer.cal_trade_result()
                    #TODO:日线和评价结果没完成，因为我没有日线数据
                    #self.analyzer.cal_daily_result()
                    #self.analyzer.print_result()